# @FileName  : tool.py
# @Time      : 2022/4/10 0010  11:44
# @Author    : LuZhaoHui
# @Software  : PyCharm

import ast
import codecs
import csv
import datetime
import locale
import os
import re
import shutil
import sys

import natsort
import openpyxl
import paramiko
import xlsxwriter
from PIL import Image
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

EXE_PATH = os.path.abspath(os.getcwd())
LOGO = os.path.join(EXE_PATH, 'logo.png').replace('\\', '/')
IMG_EXT = (".jpg")
UTF8_BASE = 0x4e00
UTF8_NUMS = 20902

# 图片信息：段标识(1-0xff)+段类型(1-IMG_XXXX)+段长度(2)+段内容(n)
# 段类型
IMG_MARK = 0xff  # 段标识
IMG_SOI = 0xd8  # 文件头
IMG_APP0 = 0xe0  # 定义交换格式和图像识别信息
IMG_APP1 = 0xe1  #
IMG_APPF = 0xef  #
IMG_DQT = 0xdb  # 定义量化表
IMG_DRI = 0xdd  # 定义重新开始间隔
IMG_SOF0 = 0xc0  # 帧开始
IMG_SOF1 = 0xc1
IMG_DHT = 0xc4  # 定义HUFFMAN表
IMG_SOS = 0xda  # 扫描行开始
IMG_EOI = 0xd9  # 文件尾
IMG_COM = 0xfe  # 注释

PAPER_LETTER = {'A0': (33.11, 46.81),
                'B1': (27.83, 39.37),
                'A1': (23.39, 33.11),
                'B2': (19.69, 27.83),
                'A2': (16.54, 23.39),
                'B3': (13.90, 19.69),
                'A3': (11.69, 16.54),
                'B4': (9.84, 13.90),
                'A4': (8.27, 11.69),
                'A5': (5.82, 8.26),
                'A6': (4.13, 5.82)}

PYNAME = 'pinyin.txt'
FTNAME = 'fanti.txt'

SPACECHAR = {'竖杠': '|', '逗号': ',', '空格': ' '}


def curFunc(err):
    return '%s:%s' % (sys._getframe().f_code.co_name, str(err))


def trimList(list):
    index = [i for i, x in enumerate(list) if x == '' or x == ' ']
    for i in index[::-1]:
        del list[i]
    return list


def getPaper(s, d):
    if d[0] > 0 and d[1] > 0:
        w = s[0] / d[0]
        h = s[1] / d[1]
        for k, v in PAPER_LETTER.items():
            v0 = v[0] / 10
            v1 = v[1] / 10
            if (v[0] - v0) < w < (v[0] + v0) and (v[1] - v1) < h < (v[1] + v1):
                return k
            if (v[0] - v0) < h < (v[0] + v0) and (v[1] - v1) < w < (v[1] + v1):
                return k
    return ''


def getDateTime(strDateTime):
    return QDateTime.fromString(strDateTime, 'yyyy-MM-dd hh:mm:ss')


def getDate(strDate):
    return QDate.fromString(strDate, 'yyyy-MM-dd')


def getTime(strTime):
    return QTime.fromString(strTime, 'hh:mm:ss')


def dateTimeToStr(datetime):
    return datetime.dateTime().toString('yyyy-MM-dd hh:mm:ss')


def dateToStr(date):
    return date.date().toString('yyyy-MM-dd')


def timeToStr(time):
    return time.time().toString('hh:mm:ss')


def nowDateTimeStr():
    return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')


def nowDateStr():
    return datetime.datetime.now().strftime('%Y-%m-%d')


def nowTimeStr():
    return datetime.datetime.now().strftime('%H:%M:%S')


def copyFile(src, des):
    dir = os.path.dirname(des)
    if not os.path.exists(dir):
        os.makedirs(dir)
    return shutil.copy(src, des)


def showBox(object, msg, title='提示'):
    QMessageBox.about(object, title, '     %s     ' % (msg))


def showBoxYes(object, msg, title='提示'):
    reply = QMessageBox.question(object,
                                 title,
                                 '     %s     ' % (msg),
                                 QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
    if reply == QMessageBox.Yes:
        return True
    return False


def natSort(flist):
    locale.setlocale(locale.LC_ALL, 'zh_CN.UTF-8')
    return natsort.natsorted(flist,
                             # key=lambda x: x.replace('(', '@').replace(')', ''),
                             key=lambda x: x.split('-'),
                             alg=natsort.ns.PATH | natsort.ns.LOCALE | natsort.ns.IGNORECASE)


def file_AB(name):
    dir = os.path.dirname(name)
    f = os.path.basename(name)
    f1 = os.path.splitext(f)[0]
    f2 = os.path.splitext(f)[1]
    a = ''
    b = ''
    if f1[-1] == 'a':
        a = 'a'
        b = 'b'
    else:
        if f1[-1] == 'b':
            a = 'b'
            b = 'a'
    if f1[-1] == 'A':
        a = 'A'
        b = 'B'
    else:
        if f1[-1] == 'B':
            a = 'B'
            b = 'A'
    if a != '' and b != '':
        f1 = f1[::-1].replace(a, b, 1)[::-1]
    return pathJoin(dir, f1 + f2)


def has_other(text):
    return re.search('^[a-zA-Z0-9-]+$', text)


def isHz(ch):
    if u'\u4e00' <= ch <= u'\u9fa5':
        return True
    return False


def has_hz(text):
    for c in text:
        if u'\u4e00' <= c <= u'\u9fa5':
            return True
    return False


def saveLog(msg, path, log=''):
    if not os.path.exists(path):
        os.makedirs(path)
    if len(log) == 0:
        file = path + '/log.txt'
    else:
        file = path + '/' + log
    if len(msg) > 0:
        with open(file, 'a', encoding='utf-8') as f:
            str = nowDateStr() + ' ' + nowTimeStr() + ' ' + msg + '\n'
            f.write(str)
        return str
    else:
        f = open(file, 'w', encoding='utf-8')
        f.close()
        return ''


def setComboBoxHeight(combo, h):
    combo.setStyleSheet("QAbstractItemView::item{height: %dpx;}" % (h))
    combo.setView(QListView())


def pathJoin(path1, path2):
    return os.path.join(path1, path2).replace('\\', '/')


def getImg0File(file):
    size = [0, 0]
    unit = 0
    density = [0, 0]
    dot = [0, 0]
    with open(file, 'rb') as f:
        l = 64 * 1024
        data = f.read(l)
        i = 0
        while i < l:
            # i += 1
            if data[i] == IMG_MARK:
                i += 1
                continue
            elif data[i] == IMG_APP0:
                # 图像识别信息
                i += 1
                ch = data[i]
                cl = data[i + 1]
                len = ch * 256 + cl
                j = i + 2
                j += 5
                j += 2
                unit = data[j]
                j += 1
                ch = data[j]
                cl = data[j + 1]
                density[0] = ch * 256 + cl
                j += 2
                ch = data[j]
                cl = data[j + 1]
                density[1] = ch * 256 + cl
                j += 2
                dot[0] = data[j]
                j += 1
                dot[1] = data[j]
                i += len
                continue
            elif data[i] == IMG_COM or (data[i] >= IMG_APP1 and data[i] <= IMG_APPF):
                # 图像识别信息
                i += 1
                ch = data[i]
                cl = data[i + 1]
                len = ch * 256 + cl
                i += len
                continue
            elif data[i] == IMG_SOI:
                i += 1
                continue
            elif data[i] == IMG_DQT:
                i += 1
                ch = data[i]
                cl = data[i + 1]
                len = ch * 256 + cl
                i += len
                continue
            elif data[i] == IMG_SOF0:
                # 图像基本信息:段标识(1,FF),段类型(1,C0),段长度(2,8＋组件数量×3)
                i += 3
                # 样本精度(1,8),图片高度(2),图片宽度(2)
                i += 1
                ch = data[i]
                i += 1
                cl = data[i]
                size[1] = ch * 256 + cl
                i += 1
                ch = data[i]
                i += 1
                cl = data[i]
                size[0] = ch * 256 + cl
                # 组件数量(1,1＝灰度图，3＝YCbCr / YIQ,4＝CMYK),（以下每个组件占用３字节）
                break
            else:
                break
    return size + density


def GetFileByte(file):
    info = QFileInfo(file)
    byte = info.size()
    return byte


def showLabelImage(area, label, name, self):
    img = QImage()
    img.load(name)
    if self:
        w = area.width()
        h = area.height()
        new_img = img.scaled(w, h, Qt.KeepAspectRatio)
        label.setPixmap(QPixmap.fromImage(new_img))
    else:
        label.setPixmap(QPixmap.fromImage(img))


def isImgOk(info):
    return 'Ok' if info[0] > 0 and info[1] > 0 and info[2] > 0 else 'Err'


def getImageSize(res):
    if res == 1:
        return [1920, 1080]
    if res == 2:
        return [2560, 1440]
    if res == 3:
        return [3840, 2160]
    return [1280, 720]


def getImgFile(file, isSet=False):
    info = [0, 0, 0, 0, 0, 'RGB', 'A4']
    b = GetFileByte(file)
    kb = int(b / 1024) + 1 if (b % 1024) else 0
    try:
        image = Image.open(file)
    except Exception:
        return (kb, 0, 0, 0, 0, 'RGB', 'A4')
    if 'dpi' in image.info:
        if image.info['dpi'][0] != 300 or image.info['dpi'][1] != 300:
            if isSet:
                image.save(file, dpi=(300, 300))
        return tuple([kb]) + image.size + (image.info['dpi'][0], image.info['dpi'][1]) + \
               tuple([image.mode, getPaper(image.size, (image.info['dpi'][0], image.info['dpi'][1]))])
    else:
        image.save(file, dpi=(300, 300))
        image = Image.open(file)
        return tuple([kb]) + image.size + (image.info['dpi'][0], image.info['dpi'][1]) + \
               tuple([image.mode, getPaper(image.size, [image.info['dpi'][0], image.info['dpi'][1]])])


def resizeImage(nameA, nameB):
    img = getImgFile(nameA, False)
    image = Image.open(nameB)
    new_image = image.resize((img[0], img[1]), Image.ANTIALIAS)
    new_image.save(nameB, dpi=(300, 300))


def showLabelImage(area, label, name, self):
    img = QImage()
    img.load(name)
    if self:
        w = area.width()
        h = area.height()
        new_img = img.scaled(w, h, Qt.KeepAspectRatio)
        label.setPixmap(QPixmap.fromImage(new_img))
    else:
        label.setPixmap(QPixmap.fromImage(img))


def writeXlsx(name, data):
    try:
        write = xlsxwriter.Workbook(name)
        sheet = write.add_worksheet()
        for i, cols in enumerate(data):
            for j, d in enumerate(cols):
                if isinstance(d, list):
                    l = len(d)
                    for k, dd in enumerate(d):
                        sheet.write(i, j * l + k, dd)
                else:
                    sheet.write(i, j, d)
        write.close()
    except Exception as err:
        print(err)


def writeCsv(name, data):
    try:
        file_csv = codecs.open(name, 'w+', 'utf-8')
        writer = csv.writer(file_csv, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
        for d in data:
            writer.writerow(d)
    except Exception as err:
        print(err)


def readXlsx(name, sheet=None, title=False):
    data = []
    nt = 0
    if os.path.exists(name):
        read = openpyxl.load_workbook(name)
        sheets = read.get_sheet_names()
        sheetData = read.get_sheet_by_name(sheet if (sheet != None) and (sheet in sheets) else sheets[0])
        rows = sheetData.rows
        n = 0
        for row in rows:
            n += 1
            if n == 1 and title:
                # 标题行
                nt += 1
                continue
            line = [col.value for col in row]
            if line[0] == '日期':
                # 标题行
                nt += 1
                continue
            data.append(line)
    return nt, data


def readXlsxSheet(name):
    sheets = []
    if os.path.exists(name):
        read = openpyxl.load_workbook(name)
        sheets = read.get_sheet_names()
    return sheets


def readCsv(name, type=[]):
    data = []
    if os.path.exists(name):
        if os.path.exists(name):
            with open(name, encoding='utf-8') as csvfile:
                csv_reader = csv.reader(csvfile)
                cmd = 'data.append(row)'
                if len(type) > 0:
                    cmd = 'data.append(['
                    for i, t in enumerate(type):
                        if t == 'int':
                            cmd += 'int(row[%d]),' % (i)
                            continue
                        if t == 'float':
                            cmd += 'float(row[%d]),' % (i)
                            continue
                        cmd += 'row[%d],' % (i)
                    cmd = cmd[:-1] + '])'
                for row in csv_reader:
                    exec(cmd)
                    # data.append(row)
    return data


def getOcrResultFile(retFile):
    file = ''
    data = []
    with open(retFile, 'r', encoding='utf-8') as f:
        d = f.read()
        dict = ast.literal_eval(d)
        file = list(dict.keys())[0]
        data = list(dict.values())[0]
    return file, data


imageColTitle = ['图片名称', '行组数', '外框坐标']
resultLineTitle = ['图片名称', '行内容', '行字数', '行外框坐标', '行类型', '字符内容(字,坐标,类型,分数)']


def getRectFromCoords(coord):
    rect = [coord[0], coord[1], coord[2], coord[7]]
    return rect


# 过滤无效符号【单引号，双引号，正反斜杠】
def filterChar(s):
    filterChar = ('\\', '\'', '"')
    # fileerFantiHz = ('戶', '記', '編')
    # fileerJantiHz = ('户', '记', '编')
    for c in filterChar:
        s = s.replace(c, " ")
    # for i, c in enumerate(fileerFantiHz):
    #     s = s.replace(c, fileerJantiHz[i])
    return s


# 获取识别结果
def getOcrResult(imgfile, data):
    # 行：
    xls_data = [{}, {}]
    file = {}
    ocr_text = ''
    for i in range(len(imageColTitle)):
        file[imageColTitle[i]] = []
    for s, f in enumerate(imgfile):
        file[imageColTitle[0]].append(f)
        file[imageColTitle[1]].append(len(data[s]['lines']))
        file[imageColTitle[2]].append(getRectFromCoords(data[s]['coords']))
    xls_data[0] = file
    lines = {}
    for i in range(len(resultLineTitle)):
        lines[resultLineTitle[i]] = []
    for s, f in enumerate(imgfile):
        chars = data[s]['lines']
        for r, value in enumerate(chars):
            # 串：
            lines[resultLineTitle[0]].append(xls_data[0][imageColTitle[0]][s])
            lines[resultLineTitle[1]].append(filterChar(value['text']))
            lines[resultLineTitle[2]].append(len(value['chars']))
            lines[resultLineTitle[3]].append(getRectFromCoords(value['coords']))
            lines[resultLineTitle[4]].append(value['type'])
            # 字：
            char = []
            for c in value['chars']:
                char.append([filterChar(c['code']), getRectFromCoords(c['coords']), c['type'], c['score']])
            lines[resultLineTitle[5]].append(char)
            # lines[resultLineTitle[5]].append(value['score'])
            ocr_text += lines[resultLineTitle[1]][r]
    xls_data[1] = lines
    return xls_data, ocr_text


class sshClient():
    def __init__(self, path):
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.path = path
        self.status = -1
        self.ip = ''
        self.port = 0

    def __del__(self):
        self.sshClose()

    def setConf(self, ip, port, user, password, workpath):
        self.sshClose()
        self.ip = ip
        self.port = port
        self.user = user
        self.password = password
        self.workPath = workpath

    def sshConnect(self):
        if self.status == 0:
            return self.status
        n = 0
        while True:
            try:
                self.ssh.connect(self.ip, self.port, self.user, self.password, timeout=1)
                self.status = 0
                self.ssh.get_transport().set_keepalive(10)
                break
            except Exception as e:
                self.status = -1
                saveLog('open sshConnect[%s:%s] 失败 %d' % (self.ip, e, n), self.path, log='error')
                if n >= 3:
                    break
            n += 1
        return self.status

    def sshClose(self):
        if self.status == 0:
            self.ssh.close()
        self.status = -1

    def sshSftpOpen(self):
        return self.ssh.open_sftp()

    def sshSftpClose(self, sftp):
        return sftp.close()

    def sshScpPutFile(self, user, ip, srcFile, desFile):
        # scp /home/dashen/Work/work_1200/*.jpg dashen@192.168.1.8:/home/test/work_1200 -v
        cmd = 'scp -q %s %s@%s:%s' % (srcFile, user, ip, desFile)
        return self.getCmd(cmd)

    def getWorkName(self, name):
        return pathJoin(self.workPath, name)

    def getSrcName(self, name):
        return pathJoin(self.path, name)

    def execCmd(self, cmd):
        str = ''
        if self.sshConnect() == 0:
            stdin, stdout, stderr = self.ssh.exec_command(cmd)
            res, err = stdout.read(), stderr.read()
            result = res if res else err
            str = result.decode()
            # self.sshClose()
        return str

    def getCmd(self, cmd):
        list = []
        if self.sshConnect() == 0:
            stdin, stdout, stderr = self.ssh.exec_command(cmd)
            res, err = stdout.read(), stderr.read()
            result = res if res else err
            str = result.decode()
            list = str.split('\n')
            list = trimList(list)
            # self.sshClose()
        return list

    def getFile(self, file):
        byte = 0
        if self.sshConnect() == 0:
            sftp_client = self.sshSftpOpen()
            try:
                stat = sftp_client.stat(self.getWorkName(file))
                byte = stat.st_size
            except Exception as e:
                byte = 0
            sftp_client.close()
            self.sshClose()
        return byte

    def putFile(self, file):
        byte = 0
        if self.sshConnect() == 0:
            sftp_client = self.sshSftpOpen()
            dir = self.getWorkName(os.path.dirname(file))
            cmd = 'mkdir "' + dir + '"'
            self.getCmd(cmd)
            srcFile = self.getSrcName(file)
            workFile = self.getWorkName(file)
            try:
                byte = sftp_client.put(srcFile, workFile).st_size
            except Exception as e:
                saveLog('put file [%s->%s] 失败[%s]' % (srcFile, workFile, str(e)), self.path, log='error')
                byte = 0
            self.sshSftpClose(sftp_client)
            self.sshClose()
        return byte


class sshTransport():
    def __init__(self, path, ip, port, user, password):
        self.path = path
        self.user = user
        self.password = password
        self.transport = paramiko.Transport((ip, port))

    def sshConnect(self):
        self.transport.connect(username=self.user, password=self.password)
        self.sftp = paramiko.SFTPClient.from_transport(self.transport)

    def readJsonFile(self, name):
        data = '{}'
        try:
            f_size = self.sftp.stat(name).st_size
            with self.sftp.open(name, "r") as f:
                data = f.read(f_size)
        except Exception as e:
            saveLog(curFunc(e), self.path, log='error')
        return data

    def sshClose(self):
        self.transport.close()

    def getFile(self, src, des, call=None):
        return self.sftp.get(src, des, callback=call)

    def putFile(self, src, des, call=None):
        return self.sftp.put(src, des, callback=call)
